package jln

import (
	oceanApi "cid-media-api/api/ocean"
	"cid-media-api/global"
	"cid-media-api/models"
	dingTalkRobot "cid-media-api/util/ding-talk-robot"
	"encoding/json"
	"fmt"
	"github.com/robfig/cron/v3"
	"github.com/samber/lo"
	"gorm.io/gorm/clause"
	"log"
	"math"
	"os"
	"strconv"
	"sync"
	"time"
)

func Run(cronSpec string) {
	c := cron.New()
	c.AddFunc(cronSpec, func() {
		// 指定日志文件路径
		logFile, err := os.OpenFile("/www/logs/cid-media-api/crontab/get_material_report_day_jln_"+time.Now().Format(time.DateOnly)+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
		if err != nil {
			log.Fatal(err)
		}
		defer logFile.Close()

		logger := log.New(logFile, "", log.LstdFlags)

		startTime := time.Now()
		logger.Printf("====================================================")
		logger.Printf("正在定时执行极乐鸟素材报表拉取任务")
		logger.Printf("任务开始时间： %v", startTime.Format(time.DateTime))

		date := time.Now().Format(time.DateOnly)

		// 如果当前时间是10:00，则执行一次昨天的数据拉取
		if startTime.Hour() == 10 && startTime.Minute() == 00 {
			logger.Printf("当前时间为10:00，正在拉取昨天的回溯数据")
			date = time.Now().AddDate(0, 0, -1).Format(time.DateOnly)
		}

		Execute(0, date, date, logger)

		endTime := time.Now()
		//计算执行时间
		duration := endTime.Sub(startTime)
		logger.Printf("任务执行时间： %v", duration)
		//判读超过3分钟，则发送钉钉通知
		if duration.Minutes() > 3 {
			_, _ = dingTalkRobot.SendMessage(fmt.Sprintf("定时执行极乐鸟素材报表拉取任务，执行时间为%v分钟，请检查日志", duration.Minutes()))
		}
		logger.Printf("任务结束时间： %v", endTime.Format(time.DateTime))
		logger.Printf("====================================================")
	})

	c.Run()
}

func Execute(advertiserID int, startDate string, endDate string, logger *log.Logger) {
	var mediaAccounts []models.MediaAccount

	insertCount := 0
	updateCount := 0

	// 查询媒体账户 状态为0，媒体ID为1，公司为极乐鸟
	mediaAccountQuery := global.CidDb.
		Model(&models.MediaAccount{}).
		Where("opt_status = ?", 0).
		Where("media_id = ?", oceanApi.MediaIdOcean).
		Where("company_id = ?", 1)
	// 如果提供了advertiserID，进一步过滤查询结果
	if advertiserID > 0 {
		mediaAccountQuery = mediaAccountQuery.Where("advertiser_id = ?", advertiserID)
	}

	// 执行查询，并将结果存入mediaAccounts中，如果出错则打印错误并返回
	err := mediaAccountQuery.Find(&mediaAccounts).Error
	if err != nil {
		print(err)
		return
	}

	// 声明一个WaitGroup，用于等待所有goroutine完成
	var wg sync.WaitGroup
	// 存放API返回的素材报表数据
	var apiData []*models.ReportDayMaterialJln
	apiDataChan := make(chan *models.ReportDayMaterialJln) // 创建通道用于传递数据

	// 遍历每个媒体账户
	for _, account := range mediaAccounts {
		wg.Add(1) // 增加计数器

		// 启动一个goroutine用于获取该媒体账户的素材报表数据
		go func(account *models.MediaAccount) {
			defer wg.Done() // 减少计数器
			const pSize int32 = 100
			var page int32 = 1

			flag := true //控制循环

			for {
				// 如果flag被设置为false, 退出循环
				if !flag {
					break
				}

				// 调用API获取素材报表数据
				res := oceanApi.GetMaterialReport(*account, startDate, endDate, page, pSize)

				// 如果返回结果为空或列表长度为0,停止循环
				if res == nil || len(res.Rows) == 0 {
					flag = false // 设置flag为false，终止本次循环
					continue
				}

				page++ // 增加页数 下一页无数据
				if page > int32(*res.PageInfo.TotalPage) {
					flag = false // 设置flag为false, 下一次不再循环
				}
				//打印总数输出
				//log.Printf("媒体账户：%v，素材报表总数：%v", account.MediaAccountId, *res.PageInfo.TotalNumber)

				// 遍历返回的报告数据
				for _, item := range res.Rows {
					// 过滤掉素材名称为空 或者为"--"的数据
					if item.Dimensions["ad_platform_material_name"] == "--" || lo.IsEmpty(item.Dimensions["ad_platform_material_name"]) {
						continue
					}

					type materialContent struct {
						VideoInfo struct {
							Img     string `json:"img"`
							VideoId string `json:"video_id"`
						} `json:"video_info"`
					}

					matContent := materialContent{}

					// 解析外层 JSON
					var outerMap map[string]interface{}
					err := json.Unmarshal([]byte(item.Dimensions["ad_platform_material_content"]), &outerMap)

					var imgUrl string
					var videoId string
					if err != nil {
						fmt.Println("Error unmarshaling outer JSON:", err)
						return
					}

					// 提取 video_info 并解析为 JSON
					var videoInfoMap map[string]interface{}
					err = json.Unmarshal([]byte(outerMap["video_info"].(string)), &videoInfoMap)
					if err != nil {
						fmt.Println("Error unmarshaling video_info JSON:", err)
						return
					}

					// 将 video_info 转换为 materialContent.VideoInfo 需要的格式
					matContent.VideoInfo.Img = videoInfoMap["img"].(string)
					matContent.VideoInfo.VideoId = videoInfoMap["video_id"].(string)
					imgUrl = matContent.VideoInfo.Img
					videoId = matContent.VideoInfo.VideoId

					promotionId, _ := strconv.ParseInt(item.Dimensions["cdp_promotion_id"], 10, 64)
					projectId, _ := strconv.ParseInt(item.Dimensions["cdp_project_id"], 10, 64)
					materialId, _ := strconv.ParseInt(item.Dimensions["material_id"], 10, 64)
					click, _ := strconv.ParseInt(item.Metrics["click_cnt"], 10, 64)
					show, _ := strconv.ParseInt(item.Metrics["show_cnt"], 10, 64)
					ctr, _ := strconv.ParseFloat(item.Metrics["ctr"], 64)
					cost, _ := strconv.ParseFloat(item.Metrics["stat_cost"], 64)
					cpm, _ := strconv.ParseFloat(item.Metrics["cpm_platform"], 64)
					cpc, _ := strconv.ParseFloat(item.Metrics["cpc_platform"], 64)
					convert, _ := strconv.ParseInt(item.Metrics["convert_cnt"], 10, 64)
					validPlay, _ := strconv.ParseInt(item.Metrics["valid_play"], 10, 64)
					totalPlay, _ := strconv.ParseInt(item.Metrics["total_play"], 10, 64)
					convertCost, _ := strconv.ParseFloat(item.Metrics["conversion_cost"], 64)
					conversionRate, _ := strconv.ParseFloat(item.Metrics["conversion_rate"], 64)
					deepConvertCnt, _ := strconv.ParseInt(item.Metrics["deep_convert_cnt"], 10, 64)
					deepConvertCost, _ := strconv.ParseFloat(item.Metrics["deep_convert_cost"], 64)
					deepConvertRate, _ := strconv.ParseFloat(item.Metrics["deep_convert_rate"], 64)
					payAmountRoi, _ := strconv.ParseFloat(item.Metrics["pay_amount_roi"], 64)
					playDuration3s, _ := strconv.ParseInt(item.Metrics["play_duration_3s"], 10, 64)
					play25FeedBreak, _ := strconv.ParseInt(item.Metrics["play_25_feed_break"], 10, 64)
					play50FeedBreak, _ := strconv.ParseInt(item.Metrics["play_50_feed_break"], 10, 64)
					play75FeedBreak, _ := strconv.ParseInt(item.Metrics["play_75_feed_break"], 10, 64)
					play99FeedBreak, _ := strconv.ParseInt(item.Metrics["play_99_feed_break"], 10, 64)

					playDuration3sRate := 0.0
					if totalPlay > 0 {
						playDuration3sRate = math.Round(float64(playDuration3s)/float64(totalPlay)) * 100
					}

					formattedRow := models.ReportDayMaterialJln{ // 将数据传递到通道
						ReportDayMaterial: models.ReportDayMaterial{
							MediaId:            account.MediaId,
							CompanyId:          account.CompanyId,
							Tdate:              item.Dimensions["stat_time_day"],
							MediaAccountId:     account.MediaAccountId,
							AdvertiserId:       account.AdvertiserId,
							AdvertiserName:     account.AdvertiserName,
							PromotionId:        promotionId,
							ProjectId:          projectId,
							PromotionName:      item.Dimensions["cdp_promotion_name"],
							ProjectName:        item.Dimensions["cdp_project_name"],
							MaterialId:         materialId,
							MaterialName:       item.Dimensions["ad_platform_material_name"],
							ImgUrl:             imgUrl,
							VideoId:            videoId,
							Click:              click,
							Cost:               cost,
							Show:               show,
							Ctr:                ctr,
							Cpm:                cpm,
							Cpc:                cpc,
							Convert:            convert,
							ValidPlay:          validPlay,
							TotalPlay:          totalPlay,
							DeepConvertCnt:     deepConvertCnt,
							DeepConvertCost:    deepConvertCost,
							DeepConvertRate:    deepConvertRate,
							ConversionRate:     conversionRate,
							ConvertCost:        convertCost,
							PayAmountRoi:       payAmountRoi,
							InAppOrderGmv:      item.Metrics["in_app_order_gmv"],
							InAppOrderRoi:      item.Metrics["in_app_order_roi"],
							PlayDuration3s:     playDuration3s,
							PlayDuration3sRate: playDuration3sRate,
							Play25FeedBreak:    play25FeedBreak,
							Play50FeedBreak:    play50FeedBreak,
							Play75FeedBreak:    play75FeedBreak,
							Play99FeedBreak:    play99FeedBreak,
						},
					}

					apiDataChan <- &formattedRow
				}
			}
		}(&account)
	}

	go func() {
		for data := range apiDataChan {
			apiData = append(apiData, data)
		}
		close(apiDataChan) // 确保在所有goroutine完成后关闭通道
	}()

	wg.Wait() // 等待所有相关的goroutine完成

	if len(apiData) == 0 {
		return // 如果apiData切片为空，则直接返回
	}

	var existedRecords []models.ReportDayMaterialJln

	global.CidDb.Model(models.ReportDayMaterialJln{}).
		Where("tdate >= ?", startDate).
		Where("tdate <= ?", endDate).
		Where("media_id = ?", oceanApi.MediaIdOcean).
		Find(&existedRecords)

	var recordsToInsert []*models.ReportDayMaterialJln
	var recordsToUpdate []*models.ReportDayMaterialJln

	for _, item := range apiData {
		if lo.SomeBy(existedRecords, func(existed models.ReportDayMaterialJln) bool {
			return existed.AdvertiserId == item.AdvertiserId &&
				existed.PromotionId == item.PromotionId &&
				existed.MaterialId == item.MaterialId &&
				lo.Substring(existed.Tdate, 0, 10) == item.Tdate &&
				existed.MediaId == item.MediaId
		}) {
			// 如果存在相同的记录，则添加进更新列表
			recordsToUpdate = append(recordsToUpdate, item)
		} else {
			// 如果不存在相同的记录，则添加进插入列表
			recordsToInsert = append(recordsToInsert, item)
		}
	}

	// 分批更新, 一次性更新太多的话, 可能报错, Error 1390 (HY000): Prepared statement contains too many placeholders
	// 这是由于 mysql 的限制, 占位符的数量不能超过 65535, 更新n条数据, 有n列的话, 最多 m * n 不能超过 65535, 所以这里分批更新
	if len(recordsToUpdate) > 0 {
		//分批更新已存在的记录
		chunks := lo.Chunk(recordsToUpdate, 1000)
		for _, chunk := range chunks {

			err = global.CidDb.Model(models.ReportDayMaterialJln{}).Clauses(clause.OnConflict{
				Columns: []clause.Column{
					{Name: "tdate"},
					{Name: "media_id"},
					{Name: "advertiser_id"},
					{Name: "promotion_id"},
					{Name: "material_id"},
				},
				DoUpdates: clause.AssignmentColumns([]string{
					"cost",
					"show",
					"cpm",
					"click",
					"ctr",
					"cpc",
					"convert",
					"convert_cost",
					"conversion_rate",
					"deep_convert_cnt",
					"deep_convert_cost",
					"pay_amount_roi",
					"video_id",
					"img_url",
					"order_count",
					"order_cost",
					"in_app_order_gmv",
					"in_app_order_roi",
					"play_duration_3s",
					"play_duration_3s_rate",
					"total_play",
					"valid_play",
					"inefficient",
					"play_25_feed_break",
					"play_50_feed_break",
					"play_75_feed_break",
					"play_99_feed_break",
				}),
			}).Create(&chunk).Error // 更新已存在的记录
			if err != nil {
				_, _ = dingTalkRobot.SendMessage(fmt.Sprintf("go极乐鸟素材，更新失败，错误信息：%v", err))
				logger.Printf("更新失败：%v", err)
			}
		}
		updateCount += len(recordsToUpdate) // 增加更新计数
	}

	if len(recordsToInsert) > 0 {
		//将recordsToInsert切片分为多个切片，每1000条记录提交一次
		chunks := lo.Chunk(recordsToInsert, 1000)
		for _, chunk := range chunks {
			err = global.CidDb.Model(models.ReportDayMaterialJln{}).Create(&chunk).Error
			if err != nil {
				_, _ = dingTalkRobot.SendMessage(fmt.Sprintf("go极乐鸟素材，插入失败，错误信息：%v", err))
				logger.Printf("插入失败：%v", err)
			}
		}
		insertCount += len(recordsToInsert) // 增加插入计数
	}

	logger.Printf("插入数量：%v", insertCount)
	logger.Printf("更新数量：%v", updateCount)
}
